<template>
  <div>
    <!-- 19 增加全屏按钮 -->
    <!-- 20 用控制面板控制全屏
       <button class="full-screen-btn" @click="onClickFullScreen">全屏</button> 
      -->
  </div>
</template>

<script>
import * as THREE from "three";
// 导入轨道控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
// 20 导入gui控制面板
import { GUI } from "three/examples/jsm/libs/lil-gui.module.min.js";

/**
 * 值类型
 * - Vector3
 * (x,y,z)
 * - Euler 欧拉: 描述一个旋转变换,通过指定轴顺序和其各个轴向上的指定旋转角度来旋转一个物体
 * (x,y,z,order) - order: 'XYZ' or 'YXZ' or 'ZXY' or 'ZYX',
 */

export default {
  data() {},
  methods: {
    // 19 增加全屏按钮
    onClickFullScreen() {
      // 渲染器画布全屏
      window.renderer && window.renderer.domElement.requestFullscreen();
      // 浏览器窗口全屏
      // document.body.requestFullscreen()
      // 退出全屏
      // document.exitFullscreen()
    },
  },
  mounted() {
    // 1 创建场景
    const scene = new THREE.Scene();

    // 2 创建相机
    const camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000,
    );
    camera.position.z = 5;
    camera.position.x = 5;
    camera.position.y = 5;
    // 3 相机指向原点
    camera.lookAt(new THREE.Vector3(0, 0, 0));

    // 4 创建一个渲染器 canvas
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    // 12 创建一个父级几何
    const parentGeometry = new THREE.BoxGeometry(); // 创建一个正方体
    const parentMaterial = new THREE.MeshBasicMaterial({ color: 0x0000ff });
    const parentCube = new THREE.Mesh(parentGeometry, parentMaterial);

    parentCube.position.x = -2;
    // 16 父元素缩小
    parentCube.scale.x = 0.5;
    parentCube.scale.z = 2;

    // 创建一个几何体Geometry和材质Material, 组合成网格Mesh
    // const geometry = new THREE.BoxGeometry(); // 创建一个正方体
    // const geometry = new THREE.CircleGeometry(1, 32); // 创建一个半径为1,分段数为32的圆
    // const geometry = new THREE.SphereGeometry(1, 32, 32); // 创建一个半径为1,分段数为32的球体
    // const geometry = new THREE.ConeGeometry(1, 2, 32); // 创建一个底面半径为1,高为2,分段数为32的圆锥体
    const geometry = new THREE.CylinderGeometry(0.5, 0.5, 1, 32); // 创建一个底面半径为0.5,高为1,分段数为32的圆柱体
    console.log("Debugger ~ mounted ~ geometry:", geometry);
    const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    const cube = new THREE.Mesh(geometry, material);

    // 11 物体的位置 - 相对于父级元素的位置
    cube.position.x = 2; // 设置物体的位置, 因为父几何在x=-2, 所以子几何在原点位置
    // cube.position.set(2, 0, 0); // 设置物体的位置

    // 15 x轴放大2倍 默认值 cube.scale.set(1,1,1)
    cube.scale.x = 2;
    // 16 局部值, 相对于父元素, 因为父元素的scale.x=0.5, 所以子元素大小不变

    // 17 物体旋转 - 局部值, 相对于父元素
    // cube.rotation instanceof THREE.Euler > true
    // cube.rotation.x = Math.PI / 4; // 绕着X轴旋转45度
    cube.rotation.set(Math.PI / 4, 0, 0, "XYZ");

    // 13 将子几何添加到父几何
    parentCube.add(cube);

    /* 5 添加到场景
    scene.add(cube);
    */

    // 14 场景添加父几何
    scene.add(parentCube);

    // 8 添加轨道控制器
    const controls = new OrbitControls(camera, renderer.domElement); // 创建轨道控制器, 第二参数表示监听事件的DOM
    // const controls = new OrbitControls(camera, document.body); // 创建轨道控制器, 第二参数表示监听事件的DOM, 监听了body后, OrbitControls会拦截所有的事件
    // 9 设置阻尼
    controls.enableDamping = true;
    controls.dampingFactor = 0.1; // 设置阻尼系数, 数值越小, 惯性越大, 数值越大, 停下来越快

    // 10 自动旋转
    controls.autoRotate = false; // 自动旋转

    // 7 添加世界坐标辅助器
    const axesHelper = new THREE.AxesHelper(5); // 辅助线长度为5
    scene.add(axesHelper);

    // 28 自定义几何体顶点,索引,面
    const customGeometry = new THREE.BufferGeometry();

    /** 34 方法二 使用共用顶点
    // 29 方法一 创建非共用的顶点数据 - 所有图形都是由三角形形成, 逆时针形成正面
    const customVertices = new Float32Array([
      0, 0, 0, 
      1, 0, 0, 
      1, 1, 0,
      
      0, 0, 0,
      1, 1, 0, 
      0, 1, 0,
    ]);
     */
    
     // 方法一 直接生成顶点数据, 更直观看到顶点数据, 但要依次全部罗列, 重复性非常高
     // 方法二 使用非重复的顶点数据集合+索引来描述几何顶点数据, 复用顶点数据, 适合复杂几何

    // 34.1 创建可共用的顶点数据集合
    const customVertices = new Float32Array([
      0, 0, 0, 
      1, 0, 0, 
      1, 1, 0, 
      0, 1, 0,
    ])

    // 30 设置几何体顶点属性 - 第一个参数是TypeArray, 第二个参数告诉THREE多少数据为一组
    customGeometry.setAttribute(
      "position",
      new THREE.BufferAttribute(customVertices, 3), // 3个数据为一组
    );

    // 34.2  创建映射顶点的索引集合
    const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);

    // 34.3 设置索引属性 - 索引集合+顶点数据集合 => 所有的顶点位置
    customGeometry.setIndex(new THREE.BufferAttribute(indices, 1))  // 1个数据为一组

    // 31 创建材质
    const customMaterial = new THREE.MeshBasicMaterial({
      color: "#ff0000",
      // wireframe: true, // 线框
      // side: THREE.DoubleSide // 正反两面可见, 否则只有正面(逆时针形成的面)可见
    });
    console.log("Debugger ~ mounted ~ customGeometry:", customGeometry);
    // 32 材质结合几何体 => 网状物
    const customMesh = new THREE.Mesh(customGeometry, customMaterial);
    // 33 将自定网状物添加到场景
    scene.add(customMesh);

    // 6 动作绘制
    function animate() {
      /* 8 添加轨道控制器 根据控制器更新画面 */
      controls.update();
      requestAnimationFrame(animate); // 请求浏览器重新绘制画面
      /* 自动旋转
      cube.rotation.x += 0.01; // x轴旋转速度增加
      cube.rotation.y += 0.01; // y轴旋转速度增加
      */
      renderer.render(scene, camera); // 渲染场景和相机设置到屏幕上
    }
    animate(); // 开始动画循环

    // 18 监听全屏事件, 让画布适配屏幕尺寸
    window.addEventListener("resize", function() {
      // 重置渲染器宽高比
      renderer.setSize(window.innerWidth, window.innerHeight);
      // 重置相机宽高比
      camera.aspect = window.innerWidth / window.innerHeight;
      // 更新相机投影矩阵
      camera.updateProjectionMatrix();
    });
    /** 20 用GUI控制面板控制全屏
    // 19 增加全屏按钮
    window.scene = scene;
    window.camera = camera;
    window.renderer = renderer;
    window.controls = controls;
    */
    // 20 GUI控制面板
    let eventObj = {
      Fullscreen: function() {
        document.body.requestFullscreen();
      },
      ExitFullscreen: function() {
        document.exitFullscreen();
      },
    };
    const gui = new GUI();
    // 第一个参数是事件对象, 第二个参数是该事件对象的key
    gui.add(eventObj, "Fullscreen").name("全屏");
    gui.add(eventObj, "ExitFullscreen").name("退出全屏");

    /** 26 用文件夹包裹子属性
    // 21 用GUI来控制几何体属性 写法1
    gui
      .add(cube.position, "x")
      .min(-10)
      .max(10)
      .step(1)
      .name("X轴");
    // 21 用GUI来控制几何体属性 写法2
    gui
      .add(cube.position, "y", -10, 10)
      .step(1)
      .name("Y轴");
    // 增加线框
    gui.add(material, 'wireframe').name('线框')
    */
    // 26 用文件夹包裹子属性
    let folder = gui.addFolder("子属性");
    folder.open();
    folder
      .add(cube.position, "x")
      .min(-10)
      .max(10)
      .step(1)
      .name("X轴");
    folder
      .add(cube.position, "y", -10, 10)
      .step(1)
      .name("Y轴");
    // 增加线框
    folder.add(material, "wireframe").name("线框");

    // 27 修改材质的颜色
    const colorParams = {
      color: "#00ff00",
    };
    folder.addColor(colorParams, "color").onChange((val) => {
      cube.material.color.set(val);
    });

    // 22 用文件夹包裹
    let parentFolder = gui.addFolder("父属性");
    // 25 默认展开
    parentFolder.open();
    parentFolder
      .add(parentCube.position, "x")
      .min(-10)
      .max(10)
      .step(1)
      .name("父X轴");
    parentFolder
      .add(parentCube.position, "y")
      .min(-10)
      .max(10)
      .step(1)
      .name("父Y轴")
      .onChange((val) => {
        console.log("父Y轴 onChange:", val);
      });
    // 23 监听事件 onChange onFinishChange
    parentFolder
      .add(parentCube.position, "z")
      .min(-10)
      .max(10)
      .step(1)
      .name("父Z轴")
      .onFinishChange((val) => {
        console.log("父Z轴 onFinishChange:", val);
      });

    // 24 切换线框模式
    parentFolder.add(parentMaterial, "wireframe").name("父线框");
  },
};
</script>

<style>
* {
  margin: 0;
  padding: 0;
}
body {
  height: 100vh;
  width: 100vw;
  overflow: hidden;
}

.full-screen-btn {
  position: fixed;
  left: 2vw;
  top: 2vh;
  z-index: 2;
}
</style>
